package es.ubu.ceudaaj.exercise.ud02.t04.l03;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * Class representing a list of contacts that can be read and written into and from an xml file.
 * 
 * This version uses DOM to process the xml.
 * 
 */
public class AgendaDOM {   
    
    private static final String XML_INPUT_PATH = "src/main/resources/xml/Contactos.xml";
    private static final String XML_OUTPUT_PATH = "src/main/resources/xml/Contactos_new.xml";
    
    // Attribute to keep a reference to the current XML in memory.
    private Document doc;
    
    /**
     * Main method responsible for printing the console menu and managing the user selections. 
     * 
     * @param args
     * @throws IOException
     */
    public static void main( String[] args ) throws IOException {
        int selectedOption = 0;
        int selectedContactId = 0;
        String search = null;
        
        // An instance of this class that will represent the current list of contacts.
        AgendaDOM agenda = new AgendaDOM();
        
        // Menu loop.
        while (true) {
            selectedContactId = 0;
            
            Menu.printMenuHeader();
            
            agenda.listContacts();
            
            Menu.printMainMenu();
            
            selectedOption = Menu.readOptionFromConsole();
            
            switch (selectedOption) {
            case 1:
                agenda.loadXML();
                Menu.pause();
                break;
            case 2:
                agenda.saveXML();
                Menu.pause();
                break;
            case 3:
                agenda.createNewContact();
                break;
            case 4:
                selectedContactId = 0;
                System.out.print("Seleccione el nº de Contacto: ");
                if ((selectedContactId = Menu.readOptionFromConsole()) > 0) {
                    agenda.printContactDetail(selectedContactId);
                } else {
                	System.out.println("No es un valor valido");
                }
                Menu.pause();
                break;
            case 5:
            	selectedContactId = 0;
                System.out.print("Seleccione el nº de Contacto:");
                if ((selectedContactId = Menu.readOptionFromConsole()) > 0) {
                    agenda.editContact(selectedContactId);
                } else {
                	System.out.println("No es un valor valido");
                }
                Menu.pause();
                break;
            case 6:
                selectedContactId = 0;
                System.out.print("Seleccione el nº de Contacto: ");
                if ((selectedContactId = Menu.readOptionFromConsole()) > 0) {
                    agenda.deleteContact(selectedContactId);
                } else {
                	System.out.println("No es un valor valido");
                }
                Menu.pause();
                break;
            case 7:
            	System.out.print("Nombre del contacto buscado: ");
            	search = Menu.readStringFromConsole();
                if ( search != null && !search.isEmpty()) {
                    agenda.searchByName(search);
                } else {
                	System.out.println("No es un valor valido");
                }
                Menu.pause();
                break;
            case 8:
            	System.out.print("Cargo del contacto buscado: ");
            	search = Menu.readStringFromConsole();
                if ( search != null && !search.isEmpty()) {
                    agenda.searchByJob(search);
                } else {
                	System.out.println("No es un valor valido");
                }
                Menu.pause();
                break;
            case 9:
            	System.out.print("C. Postal del contacto buscado: ");
            	search = Menu.readStringFromConsole();
                if ( search != null && !search.isEmpty()) {
                    agenda.searchByPostalCode(search);
                } else {
                	System.out.println("No es un valor valido");
                }
                Menu.pause();
                break;
            default:
                System.out.println("No es una opcion valida.");
                Menu.pause();
                break;
            }
        }
    }

    /**
     * Shows a list of current contacts on console. (Only name and surnames).
     */
    public void listContacts() {
    	NodeList contacts;
    	if (doc != null
    			&& (contacts = doc.getElementsByTagName("Contactos")) != null
    			&& contacts.getLength() > 0){
			contacts = doc.getElementsByTagName("Contactos");
			for (int i=0; i< contacts.getLength(); i++) {
				Element contact = (Element)contacts.item(i);
				System.out.println((i + 1) + ". " 
                        + contact.getElementsByTagName("Apellidos").item(0).getTextContent() + ", " 
						+ contact.getElementsByTagName("Nombre").item(0).getTextContent());
			}
    	} else {
            System.out.println("(lista vacia)");
        }
    }
    
    /**
     * Prints detailed information of selected user on console.
     * 
     * @param index Position of selected contact.
     * @throws IOException
     */
    public void printContactDetail(int index) throws IOException {
		NodeList contacts;
		StringBuilder sb;
		if (doc != null
				&& ((contacts = doc.getElementsByTagName("Contactos")) != null)
				&& contacts.getLength() >= index && index > 0) {
			Element cont = (Element) contacts.item(index - 1);
			sb = new StringBuilder("Detalle del contacto " + index + ": ");
			if (cont.getElementsByTagName("IdContacto") != null) {
				sb.append("\n\tID: ").append(cont.getElementsByTagName("IdContacto").item(0).getTextContent());
			}
			sb.append("\n\tNombre: ");
			if (cont.getElementsByTagName("Estimado").item(0) != null) {
				sb.append(cont.getElementsByTagName("Estimado").item(0).getTextContent());
			}
			if (cont.getElementsByTagName("Nombre").item(0) != null) {
				sb.append(cont.getElementsByTagName("Nombre").item(0).getTextContent()).append(" ");
			}
			if (cont.getElementsByTagName("Apellidos").item(0) != null) {
				sb.append(cont.getElementsByTagName("Apellidos").item(0).getTextContent());
			}
			if (cont.getElementsByTagName("Direccion").item(0) != null) {
				sb.append("\n\tDireccion: ").append(cont.getElementsByTagName("Direccion").item(0).getTextContent());
			}
			if (cont.getElementsByTagName("CodPostal").item(0) != null) {
				sb.append(", ").append(cont.getElementsByTagName("CodPostal").item(0).getTextContent());
			}
			if (cont.getElementsByTagName("Ciudad").item(0) != null) {
				sb.append(", ").append(cont.getElementsByTagName("Ciudad").item(0).getTextContent());
			}
			if (cont.getElementsByTagName("EdoOProv").item(0) != null) {
				sb.append(", ").append(cont.getElementsByTagName("EdoOProv").item(0).getTextContent());
			}
			if (cont.getElementsByTagName("Pais").item(0) != null) {
				sb.append(", ").append(cont.getElementsByTagName("Pais").item(0).getTextContent());
			}
			if (cont.getElementsByTagName("Cargo").item(0) != null) {
				sb.append("\n\tCargo: ").append(cont.getElementsByTagName("Cargo").item(0).getTextContent());
			}
			if (cont.getElementsByTagName("TelefonoTrabajo").item(0) != null) {
				sb.append("\n\tTfno. trabajo: ").append(cont.getElementsByTagName("TelefonoTrabajo").item(0).getTextContent());
			}
			if (cont.getElementsByTagName("ExtensionTrabajo").item(0) != null) {
				sb.append("\n\tExt. trabajo: ").append(cont.getElementsByTagName("ExtensionTrabajo").item(0).getTextContent());
			}
			if (cont.getElementsByTagName("NumFax").item(0) != null) {
				sb.append("\n\tFax: ").append(cont.getElementsByTagName("NumFax").item(0).getTextContent());
			}
			if (cont.getElementsByTagName("NomCorreoElectronico").item(0) != null) {
				sb.append("\n\tEmail: ").append(cont.getElementsByTagName("NomCorreoElectronico").item(0).getTextContent());
			}
			// //System.out.println("Tipo contacto: "); //TODO
			// //System.out.println("Llamadas: ");//TODO
			System.out.println(sb);
    	} else {
    		System.out.println("No existe en la lista de contactos.");
        }
    }

    /**
     * Creates a new contact and adds it to the XML representation in memory.
     * 
     * @throws IOException
     */
    public void createNewContact() throws IOException {
        InputStreamReader isr = new InputStreamReader(System.in);
        BufferedReader br = new BufferedReader(isr);
        System.out.print("Introduzca Nombre: ");
        String name = br.readLine();
        System.out.print("Introduzca Apellidos: ");
        String surnames = br.readLine();
        
        if (doc == null){
        	DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        	try {
				doc = factory.newDocumentBuilder().newDocument();
				Element dataroot = doc.createElement("dataroot");
				SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
				String date = sdf.format(new Date());
				dataroot.setAttribute("generated", date);
				doc.appendChild(dataroot);
			} catch (ParserConfigurationException e) {
				System.err.println("Error al crear el documento XML.");
			}
        }
        
        Node dataroot = doc.getElementsByTagName("dataroot").item(0);
        
        Element contact = doc.createElement("Contactos");
        
        Element contactId = doc.createElement("IdContacto");
        contactId.appendChild(doc.createTextNode(String.valueOf(doc.getElementsByTagName("Contactos").getLength() + 1)));
        contact.appendChild(contactId);
        
        Element contactSurnames = doc.createElement("Apellidos");
        contactSurnames.appendChild(doc.createTextNode(surnames));
        contact.appendChild(contactSurnames);
        
        Element contactName = doc.createElement("Nombre");
        contactName.appendChild(doc.createTextNode(name));
        contact.appendChild(contactName);

        dataroot.appendChild(contact);
    }

    /**
     * Method for editing the information for the selected contact in the XML representation in memory.
     * 
     * @param index Position of selected contact.
     * @throws IOException
     */
    public void editContact(int index) throws IOException {
    	NodeList contacts;

    	if (doc != null
    			&& ((contacts = doc.getElementsByTagName("Contactos")) != null)
    			&& contacts.getLength() >= index && index > 0){
    		
    		Element cont = (Element)contacts.item(index - 1);
    		String oldName = cont.getElementsByTagName("Nombre").item(0).getTextContent(); 
    		String oldSurnames = cont.getElementsByTagName("Apellidos").item(0).getTextContent();
    		
    		InputStreamReader isr = new InputStreamReader(System.in);
    		BufferedReader br = new BufferedReader(isr);
    		System.out.print("Introduzca Nombre (o pulse 'Enter' para mantener '"+oldName+"'): ");
    		String name = br.readLine();
    		if (name.isEmpty()) {
    			name = oldName;
    		}
    		System.out.print("Introduzca Apellidos (pulse 'Enter' para mantener '"+oldSurnames+"'): ");
    		String surnames = br.readLine();
    		if (surnames.isEmpty()) {
    			surnames = oldSurnames;
    		}
    		
    		cont.getElementsByTagName("Nombre").item(0).setTextContent(name);
    		cont.getElementsByTagName("Apellidos").item(0).setTextContent(surnames);

    		System.out.println("Se ha editado correctamente el contacto");
    	} else {
            System.out.println("No existe en la lista de contactos.");
        }
    }

    /**
     * Removes the selected contact from the XML representation in memory.
     * 
     * @param index Position of selected contact.
     */
    public void deleteContact(int index) {
    	NodeList contacts;
    	if (doc != null
    			&& ((contacts = doc.getElementsByTagName("Contactos")) != null)
    			&& contacts.getLength() >= index && index > 0){
    		Element cont = (Element)contacts.item(index - 1);
    		cont.getParentNode().removeChild(cont);
    		System.out.println("Contacto borrado.");
    	} else {
          System.out.println("No existe en la lista de contactos.");
    	}
    }
    
    /**
     * Writes the current XML in a file.
     */
    public void saveXML() {
    	if (doc != null) {
	    	TransformerFactory transformerFactory = TransformerFactory.newInstance();
			Transformer transformer;
			try {
				transformer = transformerFactory.newTransformer();
				DOMSource source = new DOMSource(doc);
				StreamResult result = new StreamResult(new File(XML_OUTPUT_PATH));
				transformer.transform(source, result);
				System.out.println("Se ha guardado correctamente el fichero " + XML_OUTPUT_PATH);
			} catch (TransformerConfigurationException e) {
				System.err.println("Error guardando el fichero XML: " + e);
			} catch (TransformerException e) {
				System.err.println("Error guardando el fichero XML." + e);
			}
    	} else {
	    	System.out.println("Aun no existe ningun contacto");
	    }
    }

    /**
     * Reads the input XML file.
     */
    public void loadXML() {
    	// Create a DOM builder and parse the fragment
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        try {
			doc = factory.newDocumentBuilder().parse(new File(XML_INPUT_PATH));
			System.out.println("Se ha leido el fichero " + XML_INPUT_PATH);
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		}
    }
        
    /**
     * Search contacts with the given name in the XML in memory.
     * 
     * Uses XPath for the search.
     *  
     * @param name Name of the contact to search.
     */
    public void searchByName(String name) {
    	String predicate = "Nombre='" + name + "'";
    	xpathSearch(predicate);
    }
    
    /**
     * Search contacts with the given job in the XML in memory.
     * 
     * Uses XPath for the search.
     *  
     * @param name Job of the contact to search.
     */
    public void searchByJob(String job) {
    	String predicate = "Cargo='" + job + "'";
    	xpathSearch(predicate);
    }
    	
    /**
     * Search contacts with the given postal code in the XML in memory.
     * 
     * Uses XPath for the search.
     *  
     * @param name Postal code of the contact to search.
     */
    public void searchByPostalCode(String postalCode) {
    	String predicate = "CodPostal='" + postalCode + "'";
    	xpathSearch(predicate);
    }

    /**
     * Prepares a query to evaluate with XPath and executes the query.
     * 
     * @param predicate The predicate specifying the specific search to do.
     */
    public void xpathSearch(String predicate) {
    	if (doc != null) {
	    	List<String> resultNames = 
	    			evaluateXPathQuery("//Contactos[" + predicate + "]/Nombre/text()");
	    	List<String> resultSurnames = 
	    			evaluateXPathQuery("//Contactos[" + predicate + "]/Apellidos/text()");
	    	
	    	if (resultNames != null && !resultNames.isEmpty()){
		    	System.out.println("Encontrados " + resultNames.size() + " resultados: ");
		    	for (int i=0; i<resultNames.size(); i++ ) {
					System.out.println(" Resultado " + (i+1) + ": " + resultNames.get(i) + " " +resultSurnames.get(i)); 
				}
	    	} else {
	    		System.out.println("No se han encontrado resultados.");
	    	}
    	} else {
    		System.out.println("Aun no hay contactos cargados");
    	}
    }

    /**
     * Performs the given XPath query against the XML in memory.
     * 
     * @param query XPath query.
     * @return
     */
    private List<String> evaluateXPathQuery(String query) {
    	List<String> results = null;
        XPathFactory factory = null;
        XPath xpath = null;
    	if (doc != null){
	    	results = new ArrayList<String>();
	        factory = XPathFactory.newInstance();
	        xpath = factory.newXPath();
	        try {
	            XPathExpression expr = xpath.compile(query);
	            Object result = expr.evaluate(doc, XPathConstants.NODESET); 
	            NodeList nodes = (NodeList) result; 
	            for (int i = 0; i < nodes.getLength(); i++) {
	                results.add(nodes.item(i).getNodeValue()); 
	            }
	        } catch (XPathExpressionException ex) {
	            System.err.println("Error al buscar el elemento: " + ex);
	        }
    	} 
        return results;
    }
    
}